﻿/* ****************************************************************************
* Copyright (c) TCC - 2009 IME - USP. 
*
* This source code is subject to terms and conditions of the Microsoft Public License. 
* A copy of the license can be found in http://msdn.microsoft.com/en-us/library/dd228915.aspx. 
* By using this source code in any fashion, you are agreeing to be bound by the terms of the 
* Microsoft Public License.
*
* You must not remove this notice, or any other, from this software.
* ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SoundLibrary.Converter
{
    public class Header
    {
        public static readonly int[][] frequencies = { new int[] { 22050, 24000, 16000, 1 }, new int[] { 44100, 48000, 32000, 1 }, new int[] { 11025, 12000, 8000, 1 } };
        public const int MPEG2_LSF = 0;
        public const int MPEG25_LSF = 2;
        public const int MPEG1 = 1;
        public const int STEREO = 0;
        public const int JOINT_STEREO = 1;
        public const int DUAL_CHANNEL = 2;
        public const int SINGLE_CHANNEL = 3;
        public const int FOURTYFOUR_POINT_ONE = 0;
        public const int FOURTYEIGHT = 1;
        public const int THIRTYTWO = 2;
        public int h_layer, h_protection_bit, h_bitrate_index, h_padding_bit, h_mode_extension;
        private int h_version;
        private int h_mode;
        private int h_sample_frequency;
        public int h_number_of_subbands, h_intensity_stereo_bound;
        public bool h_copyright, h_original;
        public sbyte syncmode;
        //public Crc16 crc;
        public short checksum;
        public int framesize;
        private int nSlots;
        public static readonly int[][][] bitrates = { new int[][] { new int[] { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0 }, new int[] { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0 }, new int[] { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0 } }, new int[][] { new int[] { 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0 }, new int[] { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0 }, new int[] { 0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0 } }, new int[][] { new int[] { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0 }, new int[] { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0 }, new int[] { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0 } } };

        private sbyte[] syncbuffer;
        private Mp3Stream source;

        public Header(Mp3Stream stream)
        {
            this.syncmode = 0;
            this.syncbuffer = new sbyte[4];
            this.source = stream;
        }

        public int Layer
        {
            get { return h_layer; }
            set { h_layer = value; }
        }

        public int Frequency()
        {
            return frequencies[h_version][h_sample_frequency];
        }

        public int ModeExtension
        {
            get { return h_mode_extension; }
            set { h_mode_extension = value; }
        }

        public int Slots
        {
            get { return nSlots; }
            set { nSlots = value; }
        }

        public int Mode
        {
            get { return h_mode; }
            set { h_mode = value; }
        }

        public int Version
        {
            get { return h_version; }
            set { h_version = value; }
        }

        public int SampleFrequency
        {
            get { return h_sample_frequency; }
            set { h_sample_frequency = value; }
        }

        public bool GetNextHeader()
        {
            int headerstring;
            int channel_bitrate;

            bool sync = false;

            do
            {
                headerstring = this.SyncHeader(syncmode);

                if (headerstring == 0) // terminou o arquivo
                    return false;

                if (syncmode == BitStream.INITIAL_SYNC)
                {
                    h_version = ((Util.URShift(headerstring, 19)) & 1);
                    if (((Util.URShift(headerstring, 20)) & 1) == 0)
                        if (h_version == MPEG2_LSF)
                            h_version = MPEG25_LSF;
                }

                h_layer = 4 - (Util.URShift(headerstring, 17)) & 3;
                h_protection_bit = (Util.URShift(headerstring, 16)) & 1;
                h_bitrate_index = (Util.URShift(headerstring, 12)) & 0xF;
                h_padding_bit = (Util.URShift(headerstring, 9)) & 1;
                h_mode = ((Util.URShift(headerstring, 6)) & 3);
                h_mode_extension = (Util.URShift(headerstring, 4)) & 3;
                if (h_mode == JOINT_STEREO)
                {
                    h_intensity_stereo_bound = (h_mode_extension << 2) + 4;
                }
                else
                {
                    h_intensity_stereo_bound = 0;
                }

                if (((Util.URShift(headerstring, 3)) & 1) == 1)
                {
                    h_copyright = true;
                }

                if (((Util.URShift(headerstring, 2)) & 1) == 1)
                {
                    h_original = true;
                }

                if (h_layer == 1)
                {
                    h_number_of_subbands = 32;
                }
                else
                {
                    channel_bitrate = h_bitrate_index;
                    if (h_mode != SINGLE_CHANNEL)
                        if (channel_bitrate == 4)
                            channel_bitrate = 1;
                        else
                            channel_bitrate -= 4;

                    if ((channel_bitrate == 1) || (channel_bitrate == 2))
                        if (h_sample_frequency == THIRTYTWO)
                            h_number_of_subbands = 12;
                        else
                            h_number_of_subbands = 8;
                    else if ((h_sample_frequency == FOURTYEIGHT) || ((channel_bitrate >= 3) && (channel_bitrate <= 5)))
                        h_number_of_subbands = 27;
                    else
                        h_number_of_subbands = 30;
                }
                if (h_intensity_stereo_bound > h_number_of_subbands)
                    h_intensity_stereo_bound = h_number_of_subbands;

                this.framesize = CalculateFramesize();

                if (this.IsNextPositionSynchronizedMark(syncmode, framesize))
                {
                    if (syncmode == BitStream.INITIAL_SYNC)
                    {
                        syncmode = BitStream.STRICT_SYNC;
                    }
                    sync = true;
                }
            }
            while (!sync);

            return true;
        }

        private bool IsSynchronizedMark(int headerstring, int syncmode)
        {
            bool sync = false;

            //if (syncmode == INITIAL_SYNC)
            if (syncmode == 0)
            {
                sync = (headerstring & 0xFFE00000) == 0xFFE00000;
            }
            else
            {
                // precisa alterar essa porra!!!!
                //sync = ((headerstring & 0xFFE00000) == 0xFFE00000)
                //&& (((headerstring & 0x000000C0) == 0x000000C0) == this.single_ch_mode);
                sync = ((headerstring & 0xFFE00000) == 0xFFE00000)
                && (((headerstring & 0x000000C0) == 0x000000C0) == false);
            }

            if (sync)
            {
                sync = (Util.URShift(headerstring, 10) & 3) != 3;
                if (!sync)
                {
                    Trace.WriteLine("INVALID SAMPLE RATE DETECTED", "Bitstream");
                }
            }

            if (sync)
            {
                sync = (Util.URShift(headerstring, 17) & 3) != 0;
                if (!sync)
                {
                    Trace.WriteLine("INVALID LAYER DETECTED", "Bitstream");
                }
            }

            if (sync)
            {
                sync = (Util.URShift(headerstring, 19) & 3) != 1;
                if (!sync)
                {
                    Console.WriteLine("INVALID VERSION DETECTED");
                }
            }

            return sync;
        }

        private int SyncHeader(sbyte syncmode)
        {
            bool sync;
            int headerstring;

            int bytesRead = this.source.Read(this.syncbuffer, 0, 3);

            // terminou o arquivo de leitura
            if (bytesRead < 3)
                return 0;

            headerstring = ((this.syncbuffer[0] << 16) & 0x00FF0000) | ((this.syncbuffer[1] << 8) & 0x0000FF00) | ((this.syncbuffer[2] << 0) & 0x000000FF);

            do
            {
                headerstring <<= 8;
                if (this.source.Read(this.syncbuffer, 3, 1) <= 0)
                {
                    return 0;
                }
                headerstring |= this.syncbuffer[3] & 0x000000FF;

                sync = this.IsSynchronizedMark(headerstring, syncmode);
            }
            while (!sync);

            return headerstring;
        }

        private bool IsNextPositionSynchronizedMark(int syncmode, int framesize)
        {
            this.source.Forward(framesize);
            int read = this.source.Read(this.syncbuffer, 0, 4);
            int headerstring = (int)((this.syncbuffer[0] << 24) & 0xFF000000) | ((this.syncbuffer[1] << 16) & 0x00FF0000) | ((this.syncbuffer[2] << 8) & 0x0000FF00) | ((this.syncbuffer[3] << 0) & 0x000000FF);
            this.source.Rewind(framesize + 4);

            return this.IsSynchronizedMark(headerstring, syncmode);
        }

        private int CalculateFramesize()
        {
            int frameSize;

            if (h_layer == 1)
            {
                frameSize = (12 * bitrates[h_version][0][h_bitrate_index]) / frequencies[h_version][h_sample_frequency];
                if (h_padding_bit != 0)
                    frameSize++;
                frameSize <<= 2;
                nSlots = 0;
            }
            else
            {
                frameSize = (144 * bitrates[h_version][h_layer - 1][h_bitrate_index]) / frequencies[h_version][h_sample_frequency];
                if (h_version == MPEG2_LSF || h_version == MPEG25_LSF)
                    frameSize >>= 1;
                if (h_padding_bit != 0)
                    frameSize++;
                if (h_layer == 3)
                {
                    if (h_version == MPEG1)
                    {
                        nSlots = frameSize - ((h_mode == SINGLE_CHANNEL) ? 17 : 32) - ((h_protection_bit != 0) ? 0 : 2) - 4; // header size
                    }
                    else
                    {
                        nSlots = frameSize - ((h_mode == SINGLE_CHANNEL) ? 9 : 17) - ((h_protection_bit != 0) ? 0 : 2) - 4; // header size
                    }
                }
                else
                {
                    nSlots = 0;
                }
            }
            frameSize -= 4;
            return frameSize;
        }
        
    }
}
